package com.bac.rentmap.web.controller.ad.search;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.datanucleus.util.StringUtils;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;

import com.bac.rentmap.ad.flat.Constants;
import com.bac.rentmap.ad.flat.FlatAd;
import com.bac.rentmap.ad.flat.LocalAddress;
import com.bac.rentmap.ad.flat.crawler.localaddress.LocalAddressParser;
import com.bac.rentmap.ad.flat.rating.FlatAdRatingCalculator;
import com.bac.rentmap.ad.flat.search.FlatAdSearcher;
import com.bac.rentmap.ad.flat.search.SearchParameters;
import com.bac.rentmap.manager.BuildingManager;
import com.bac.rentmap.map.thumbnail.MapThumbnailService;
import com.bac.rentmap.map.thumbnail.Point;
import com.bac.rentmap.model.Building;
import com.bac.rentmap.web.servlet.AcceptHeaderViewResolver;
import com.bac.rentmap.web.util.CityParser;
import com.bac.rentmap.web.util.PathVariableParser;
import com.google.appengine.api.datastore.GeoPt;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

/**
 * Searches for flat ads and put this information to model.
 *
 * @author Andrew Osipenko
 */
public class FlatAdSearchController extends DummyFlatAdSearchController implements BeanNameAware 
{
    private static final Logger log = Logger
            .getLogger(FlatAdSearchController.class.getName());

    private FlatAdSearcher flatAdSearcher;
    private int pageSize;
    private LocalAddressParser localAddressParser;
    private BuildingManager buildingManager;
    private FlatAdRatingCalculator flatAdRatingCalculator;
    private MapThumbnailService mapThumbnailService;
    private Pattern pattern;
    private boolean pricePerDay;

    protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object parameters, BindException errors)
            throws Exception {
        log.fine("About to search flat ads");
        ModelAndView res = super.onSubmit(parameters);
        SearchParameters searchParameters = (SearchParameters) parameters;
        String city = CityParser.parseCityFromDomain(request);
        
        searchParameters.setPageSize(pageSize);
        List<String> pathVariables = PathVariableParser.getPathVariables(request.getServletPath(), pattern);
        if(!pathVariables.isEmpty()){
            if(city == null && pathVariables.size() == 2){
                city = pathVariables.get(0);
            }
            int urlPage = Integer.valueOf(pathVariables.get(pathVariables.size() - 1));
            if(searchParameters.getPage() == 0 && urlPage != 0)
                searchParameters.setPage(urlPage);
        }
        if(city != null){
            searchParameters.setCity(city);
        }
        request.setAttribute("city", searchParameters.getCity());
        
        if (errors.hasErrors()) {
            log.finer("Bind errors: " + errors.getAllErrors());
        }
        else{
            if("text/xml".equals(request.getHeader(AcceptHeaderViewResolver.HEADER_NAME))){
                searchParameters.setSourceUrl(Constants.INTERNAL_FLAT_AD_SOURCE_URL); // do not display external ads for android app
            }
            searchParameters.setPricePerDay(pricePerDay);
            List<FlatAd> flatAds = flatAdSearcher.search(searchParameters);
            res.addObject("flatAds", flatAds);
            res.addObject("normalizedRatings", normalizeRatings(flatAds));
            if(mapThumbnailService != null)
                res.addObject("thumbnailPoints", calculateThumbnailCoordinates(flatAds));
            res.addObject("hasNextPage", flatAds.size() == searchParameters
                    .getPageSize());
            UserService userService = UserServiceFactory.getUserService();
            if(userService.isUserLoggedIn()){
                res.addObject("loggedIn", Boolean.TRUE);
            }
        }
        return res;
    }
    
    private List<Double> normalizeRatings(List<FlatAd> flatAds){
        List<Long> ratings = new ArrayList<Long>(flatAds.size());
        for (FlatAd flatAd : flatAds) {
            if(flatAd != null){
                Boolean ratingCalculated = flatAd.getRatingCalculated();
                if(ratingCalculated != null && ratingCalculated){
                    ratings.add(flatAd.getRating());
                }
                else{
                    ratings.add(null);
                }
            }
            else{
                ratings.add(null);
            }
        }
        return flatAdRatingCalculator.normalizeRatings(ratings);
    }
    
    private List<Point> calculateThumbnailCoordinates(List<FlatAd> flatAds){
        List<Point> res = new ArrayList<Point>(flatAds.size());
        for (FlatAd flatAd : flatAds) {
            if(flatAd != null && flatAd.lon != null && flatAd.lat != null){
                res.add(mapThumbnailService.calculateThumbnailCoordinates(flatAd.getCity(), flatAd.lon, flatAd.lat));
            }
            else{
                res.add(null);
            }
        }
        return res;
    }

    @Override
    protected void onBindAndValidate(HttpServletRequest request,
            Object parameters, BindException errors) throws Exception {
        super.onBindAndValidate(request, parameters, errors);
        SearchParameters searchParameters = (SearchParameters) parameters;
        if (searchParameters.getLat() == null
                && searchParameters.getLon() == null) {
            LocationAwareSearchParameters locationAwareSearchParameters = (LocationAwareSearchParameters) searchParameters;
            if (!StringUtils.isEmpty(locationAwareSearchParameters
                    .getLocationLabel())) {
                // Address was entered not from easysearch but manually
                GeoPt geoPt = calculateLocationGeoPt(searchParameters.getCity(),
                        locationAwareSearchParameters.getLocationLabel(),
                        errors);
                if (geoPt != null) {
                    locationAwareSearchParameters.setLat(new Double(geoPt
                            .getLatitude()));
                    locationAwareSearchParameters.setLon(new Double(geoPt
                            .getLongitude()));
                }
            }
        }

    }

    private GeoPt calculateLocationGeoPt(String city, String locationLabel,
            BindException errors) {
        try {
            LocalAddress localAddress = localAddressParser.parse(locationLabel);
            Building building = buildingManager
                    .findBuilding(city, localAddress);
            if (building != null) {
                return building.getGeoPt();
            } else {
                handleLocationLabelError(locationLabel, errors);
            }
        } catch (IllegalArgumentException e) {
            handleLocationLabelError(locationLabel, errors);
        }
        return null;
    }

    private void handleLocationLabelError(String locationLabel,
            BindException errors) {
        log.info("Can't find building geo location for " + locationLabel);
        errors.rejectValue("locationLabel", "error.cantEvaluateGeoPt", new Object[]{locationLabel}, "Can't find address");
    }

//    @Override
//    public long getLastModified(HttpServletRequest request) {
//        Calendar calendar = Calendar.getInstance();
//        calendar.set(Calendar.MINUTE, 0);
//        calendar.set(Calendar.SECOND, 0);
//        calendar.set(Calendar.MILLISECOND, 0);
//        return calendar.getTimeInMillis();
//    }

    @Required
    public void setFlatAdSearcher(FlatAdSearcher flatAdSearcher) {
        this.flatAdSearcher = flatAdSearcher;
    }

    @Required
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    @Required
    public void setLocalAddressParser(LocalAddressParser localAddressParser) {
        this.localAddressParser = localAddressParser;
    }

    @Required
    public void setBuildingManager(BuildingManager buildingManager) {
        this.buildingManager = buildingManager;
    }

    @Required
    public void setFlatAdRatingCalculator(
            FlatAdRatingCalculator flatAdRatingCalculator) {
        this.flatAdRatingCalculator = flatAdRatingCalculator;
    }
    public void setMapThumbnailService(MapThumbnailService mapThumbnailService) {
        this.mapThumbnailService = mapThumbnailService;
    }
    @Override
    public void setBeanName(String beanName) {
        pattern = PathVariableParser.createPattern(beanName);
    }

    public void setPricePerDay(boolean pricePerDay) {
        this.pricePerDay = pricePerDay;
    }
}


